//
//  NMEventController.swift
//  NetEaseMusic
//
//  Created by SAGESSE on 2020/1/13.
//  Copyright © 2020 SAGESSE. All rights reserved.
//

import UIKit
import AsyncDisplayKit


class NMEventController: ASViewController<ASTableNode>, ASTableDataSource, ASTableDelegate {
    
    var player: NMEventControllerAutoPlayer = .init()
    
    override init(node: ASTableNode) {
        super.init(node: node)
        node.dataSource = self
        node.delegate = self
    }
    
    required init?(coder: NSCoder) {
        super.init(node: ASTableNode())
        node.dataSource = self
        node.delegate = self
    }
    
    var token: String = "-1"
    var events: [NMEvent] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let iv = UIImageView()
        iv.backgroundColor = NMTheme.shared.color(forKey: "event.background")
        self.node.view.backgroundColor = nil
        self.node.view.backgroundView = iv
    }
    
    
    func loadMore(_ complete: @escaping (Array<NMEvent>?, Error?) -> ()) {
        
//        let url = "/api/event/get/66780306" // self
//        let url = "/api/event/get/55535424" //welphenEDM
        //let url = "/api/event/get/1435028069"
//        let url = "/api/event/get/9003" // has more image
        //let url = "/api/event/get/2082221" // has more text
//        let url = "/api/event/get/351218439"
//        let url = "/api/event/get/247883585" // 帐号己注销
//        let url = "/api/event/get/452096615" // 柚乃yuuno
//        let url = "/api/event/get/110878486" // 逆时针的旋转
//        if token == "-1" {
//            //token = "1534868560664"
////            token = "1480608000000"
//            token = "1517831060431"
//        }
        
        
//        let url = "/api/event/get/351363741" // 红毛要九_biu
//        let url = "/api/event/get/253335632" // 云音乐客服
//        let url = "/api/event/get/126014458" // 程篱笆
//        let url = "/api/event/get/78905899" // 樱井莜木
//        let param = [
//            "limit": "20",
//            "time": token as Any
//        ]
        //https://music.163.com/user/home?id=1435028069
        //https://music.163.com/user/home?id=351218439
        
        let url = "/api/v2/event/get"
        let param = [
            "pagesize": "20",
            "lasttime": token as Any
        ]
        
        NMNetwork.shared.request(url, param) {
            do {
                var key = "events"
                if url == "/api/v2/event/get" {
                    key = "event"
                }
                
                // The prase remote resource returns the result.
                guard let events = try $0?.decode([NMEvent].self, forKey: key) else {
                    throw $1 ?? URLSessionEx.error(NSURLErrorBadServerResponse)
                }
                
                // Load the paging info.
//                self.token = ""
//                if try $0?.decodeIfPresent(Bool.self, forKey: "more") ?? false {
                    self.token = try $0?.decodeIfPresent(String.self, forKey: "lasttime") ?? ""
//                }

                // Request is completed.
                complete(events.filter({ !$0.entitys.isEmpty }), nil)
                
                // Delayed processing is required because the cell node is not ready for display.
                DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000)) {
                    if self.interfaceState == .inHierarchy {
                        self.scrollViewDidScroll(self.node.view)
                    }
                }
                
            } catch (let error) {
                // A fatal error occurred
                print(error)
                complete(nil, error)
            }
        }
    }
    
    
    func tableNode(_ tableNode: ASTableNode, numberOfRowsInSection section: Int) -> Int {
        return events.count
    }
    
    func tableNode(_ tableNode: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock {
        return { [unowned self] in
            let event = self.events[indexPath.row] //NMEvent(indexPath.item)
            let node = NMEventNode(for: event)
            //node.backgroundColor = .white// .random
            func fill(_ node: ASDisplayNode) {
//                node.borderWidth = 1 / UIScreen.main.scale
//                node.borderColor = UIColor.red.cgColor
                if node.backgroundColor == nil {
                    node.backgroundColor = UIColor.black.withAlphaComponent(0.2)
                }
                node.subnodes?.forEach(fill)
            }
//            fill(node)
            return node
        }
    }
    
    func tableNode(_ tableNode: ASTableNode, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
        return true
    }
    
    func tableNode(_ tableNode: ASTableNode, didSelectRowAt indexPath: IndexPath) {
        tableNode.deselectRow(at: indexPath, animated: true)
    }
    
    func tableNode(_ tableNode: ASTableNode, willDisplayRowWith node: ASCellNode) {
        // If the node not support playable, ignore this node.
        if player.isEnabled, let event = node as? NMEventNode, !event.playables.isEmpty {
            player.add(event, for: event.view.convert(event.bounds, to: tableNode.view))
        }
    }
    func tableNode(_ tableNode: ASTableNode, didEndDisplayingRowWith node: ASCellNode) {
        // If the node not support playable, ignore this node.
        if player.isEnabled, let event = node as? NMEventNode, !event.playables.isEmpty {
            player.remove(event)
        }
    }
    
    func shouldBatchFetch(for tableNode: ASTableNode) -> Bool {
        return !token.isEmpty
    }
    
    func tableNode(_ tableNode: ASTableNode, willBeginBatchFetchWith context: ASBatchContext) {
        loadMore {
            
            guard let newValue = $0 else {
                // A fatal error occurred.
                self.logger.debug?.write($1 ?? "")
                context.completeBatchFetching(false)
                return
            }
            
            ASPerformBlockOnMainThread {
                let e = self.events.count
                self.events.append(contentsOf: newValue)
                if e != 0 {
                    self.node.insertRows(at: newValue.enumerated().map({ IndexPath(row: e + $0.offset, section: 0) }), with: .fade)
                } else {
                    self.node.reloadData()
                }
                context.completeBatchFetching(true)
            }
        }
    }
    
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
//        guard NMNetwork.State.isWifi else {
//            player.stop()
//            return
//        }
        // When player item is empty, no needs calculate.
        if player.isEnabled, !player.isEmpty {
            player.play(scrollView)
        }
    }
    
    override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
        return .none
    }
    
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return .default
    }
    
    override var prefersStatusBarHidden: Bool {
        return false
    }
}


//class NMEventAvatarNode: ASDisplayNode {
//
//    let avatar: ASImageNode = .init()
//
//    let background: ASImageNode = .init()
//    let foreground: ASImageNode = .init()
//
//    let badge: ASImageNode = .init()
//    let decorator: ASImageNode = .init()
//
//    var padding: UIEdgeInsets = .init(top: 9, left: 9, bottom: 9, right: 9)
//
//    override init() {
//        super.init()
//
//        self.addSubnode(self.background)
//        self.addSubnode(self.avatar)
//        self.addSubnode(self.foreground)
//        self.addSubnode(self.decorator)
//        self.addSubnode(self.badge)
//
//        self.style.preferredSize = CGSize(width: 58, height: 58)
//
//        self.background.backgroundColor = .random
//        self.background.cornerRadius = max(self.style.preferredSize.width - padding.left - padding.right - 2, 0) / 2
//
//        self.badge.backgroundColor = .random
//        self.badge.cornerRadius = 8
//
//        self.badge.style.preferredSize = .init(width: 16, height: 16)
//    }
//
//    /// Setup layout for all subnodes.
//    override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
//        return ASOverlayLayoutSpec.overlay(
//            background.insets(top: padding.top + 1, left: padding.left + 1, bottom: padding.bottom + 1, right: padding.right + 1),
//            avatar.insets(padding),
//            foreground.insets(padding),
//            decorator,
//            badge.insets(top: .infinity, left: .infinity, bottom: padding.bottom, right: padding.right - 5)
//        )
//    }
//}


// MARK: -


/// The protocol defines whether the display node supports autoplay.
protocol NMEventControllerAutoPlayable: class {

    var isPlayable: Bool { get }
    
    func play()
    func stop()
}


class NMEventControllerAutoPlayer {
    
    typealias Key = CGRect
    typealias Value = NMEventNode
    
    class Element {
        
        var key: Key
        var value: Value
        var sublayouts: [Key]?

        unowned(unsafe)
        var prev: Element?
        var next: Element?
        
        init(value: Value, for key: Key) {
            self.key = key
            self.value = value
        }
    }
    class Scheduler {

        func delay(_ seconds: TimeInterval, callback: @escaping () -> ()) {
            // Reuse actived timer.
            if self.timer == nil {
                let timer = Timer.scheduledTimer(timeInterval: seconds, target: self, selector: #selector(tick), userInfo: nil, repeats: false)
                RunLoop.main.add(timer, forMode: .tracking)
                self.timer = timer
            }
            
            // Reset the callback and timer fire date.
            self.timer?.fireDate = Date(timeIntervalSinceNow: seconds)
            self.callback = callback
        }
        
        @objc
        private func tick() {
            // Clear the set time to prevent circular references.
            self.timer?.invalidate()
            self.timer = nil
            // Perform user callback.
            self.callback?()
            self.callback = nil
        }
        
        private var timer: Timer?
        private var callback: (() -> ())?
    }
    
    var isEmpty: Bool {
        return head == nil
    }
    var isPlaying: Bool {
        return cur != nil
    }

    var isEnabled: Bool = true
        
    func add(_ value: Value, for key: Key) {
        // Look up the left node.
        let node = Element(value: value, for: key)
        guard let left = find(forKey: key) else {
            // The left node not found, add to head.
            node.next = head
            head?.prev = node
            head = node
            if tail == nil {
                tail = node
            }
            return
        }
        
        // When node change must adjus node chain.
        let right = left.next
        right?.prev = node
        node.next = left.next
        left.next = node
        node.prev = left
        
        // Move tail node cursor.
        if tail === left {
            tail = node
        }
    }
    func remove(_ value: Value) {
        // The key exists in the chain.
        guard let node = find(forValue: value) else {
            return
        }
        
        // When node change must adjus node chain.
        node.prev?.next = node.next
        node.next?.prev = node.prev
        
        // Head node is removed, reset head.
        if head === node {
            head = node.next
        }
        // Tail node is revmoved, reset tail.
        if tail === node {
            tail = node.prev
        }
        
        // If the removed node is playing, must stop playing.
        if cur?.node === node {
            stop()
        }
    }
    
    func play(_ scrollView: UIScrollView) {
        // Must need to compute the insets.
        let edg: UIEdgeInsets
        
        if #available(iOS 11.0, *) {
            edg = scrollView.adjustedContentInset
        } else {
            edg = scrollView.contentInset
        }
        
        // Plays all playable nodes in the visable rect.
        play(.init(x: 0, y: scrollView.contentOffset.y + edg.top, width: scrollView.frame.width, height: scrollView.frame.height - edg.top - edg.bottom))
    }
    
    func play(_ visible: CGRect) {
        // Find the first playable node.
        guard let (node, playable) = find(in: visible) else {
            // When no any node found, stop the current playing node if needed.
            return stop()
        }
        
        // Ignore the current play node if it has not changes.
        guard cur?.node !== node || cur?.playable !== playable else {
            return
        }
        
        // Stop play immediately.
        stop()
        cur = (node, playable)

        // Start play in 800ms after.
        scheduler.delay(0.8) {
            // If current node is changes, ignore this action.
            guard self.cur?.playable === playable else {
                return
            }
            self.active = playable
            playable.play()
        }
    }
    func stop() {
        cur = nil
        active?.stop()
        active = nil
    }
    
    private func find(forKey key: Key) -> Element? {
        
        var left = head
        var right = tail
        
        while left != nil || right != nil {

            if let right = right, right.key.minY < key.minY {
                return right
            }
            if left === right {
                return left?.prev
            }
            if let left = left, left.key.minY > key.minY {
                return left.prev
            }
            if left?.next === right {
                return left
            }
            
            left = left?.next
            right = right?.prev
        }
        
        return nil
    }
    private func find(forValue value: Value) -> Element? {
        
        var left = head
        var right = tail
        
        while left != nil || right != nil {

            if let right = right, right.value === value {
                return right
            }
            if left === right {
                return nil
            }
            if let left = left, left.value === value {
                return left
            }
            if left?.next === right {
                return nil
            }
            
            left = left?.next
            right = right?.prev
        }
        
        return nil
    }
    
    private func find(in visible: CGRect) -> (Element, NMEventControllerAutoPlayable)? {
        
        var left = head
        
        while let event = left, !event.value.playables.isEmpty {
            // Ignore the event when the rect displayed do not intersect.
            if !visible.intersects(event.key) {
                left = left?.next
                continue
            }
            
            // Compute all the layout information for playable node.
            if event.sublayouts == nil {
                event.sublayouts = event.value.playables.compactMap {
                    return ($0 as? ASDisplayNode).map {
                        return $0.convert($0.bounds, to: event.value)
                    }
                }
            }
            
            // If the minY than half of the playable node, can't star this node.
            let minY = visible.minY - event.key.minY
            let maxY = minY + visible.height
            guard let index = event.sublayouts?.firstIndex(where: { minY <= $0.midY && $0.midY <= maxY }) else {
                left = left?.next
                continue
            }
            return (event, event.value.playables[index])
        }
        return nil
    }
    
    private var head: Element?
    private var tail: Element?
    
    private var cur: (node: Element, playable: NMEventControllerAutoPlayable)?
    private var active: NMEventControllerAutoPlayable?
    private var scheduler: Scheduler = .init()
}

